﻿// HSS.Interlink.UploadFileDialogViewModel.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.html)
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       UploadFileDialogViewModel.cs
// Author:     HSS\gbanta
// Created:    07/16/2011
// Modified:   07/22/2011
// ----------------------------------------------------------------------------
namespace HSS.Interlink
{
	#region Using Directives
	using System;
	using System.Collections.ObjectModel;
	using System.Collections.Specialized;
	using System.ComponentModel;
	using System.Linq;
	using System.Collections.Generic;
	using System.Threading;
	using System.Windows.Data;
	using System.Windows;
	using System.Windows.Media;
	using System.Windows.Input;
	using System.Windows.Threading;
	using System.Windows.Controls;
	using System.IO;

	#endregion

	#region UploadFileDialogViewModel
	/// <summary>
	/// UploadFileDialogViewModel
	/// </summary>
	public sealed class UploadFileDialogViewModel : INotifyPropertyChanged
	{
		#region Events

		#region INotifyPropertyChanged Members
		/// <summary>
		/// Notifies clients that a property value has changed
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;
		/// <summary>
		/// Raises the UploadFileDialogViewModel.PropertyChanged event with the provided arguments
		/// </summary>
		/// <param name="property">The name of the Property that changed</param>
		private void OnPropertyChanged(string property)
		{
			this.Invoke(() =>
				{
					var handler = this.PropertyChanged;
					if (null == handler)
						return;
					handler(this, new PropertyChangedEventArgs(property));
				});
		}
		#endregion

		#endregion

		#region Fields
		static readonly object progressLock = new object();
		internal UploadFileDialog view;
		ChildWindow fileDetail;
		internal Dispatcher dispatcher;
		const double defaultWidth = 215;
		double fileNameWidth = defaultWidth;
		long totalBytesSent;
		long totalBytes;
		bool hasStarted;
		int currentCount;
		int maxConcurrentFiles;
		#endregion

		#region Constructor
		/// <summary>
		/// UploadFileDialogViewModel constructor
		/// </summary>
		/// <param name="view">The UploadFileDialog instance.</param>
		internal UploadFileDialogViewModel(UploadFileDialog view)
		{
			this.view = view;
			this.dispatcher = view.Dispatcher;
			this._files = new ObservableCollection<UploadFileViewModel>();
			this._files.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(UploadFiles_CollectionChanged);
			this.State = UploadState.NotStarted;
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets whether or not the user can begin a new upload after the current upload request completes.
		/// </summary>	
		public bool AllowNewUpload { get; private set; }
		/// <summary>
		/// Gets whether or not the user can select more than one file.
		/// </summary>
		public bool AllowMultiSelect { get; private set; }
		/// <summary>
		/// Gets the state of the Upload Manager
		/// </summary>
		public UploadState State
		{
			get { return _state; }
			private set
			{
				if (_state != value)
				{
					_state = value;
					this.OnPropertyChanged("State");
				}
			}
		} UploadState _state;
		/// <summary>
		/// Gets the ObservableCollection of UserFiles
		/// </summary>
		public ObservableCollection<UploadFileViewModel> Files
		{
			get { return _files; }
		} ObservableCollection<UploadFileViewModel> _files;
		/// <summary>
		/// Gets if the upload has completed
		/// </summary>
		public bool IsComplete
		{
			get { return _isComplete; }
			private set
			{
				_isComplete = value;
				if (value)
					this.OnPropertyChanged("IsComplete");
			}
		} bool _isComplete = false;
		/// <summary>
		/// Gets the Status formatted as a friendly statement
		/// </summary>
		public string StatusFormatted
		{
			get
			{
				string statusStr = string.Empty;
				switch (this.State)
				{
					case UploadState.CanceledDone:
					case UploadState.Canceled:
						statusStr = "Upload was canceled";
						break;

					case UploadState.CompletedDone:
					case UploadState.Completed:
						statusStr = "Upload completed";
						break;

					case UploadState.NotStarted:
						statusStr = "Upload not started";
						break;

					case UploadState.PartialCompleteDone:
					case UploadState.PartialComplete:
						statusStr = "Upload partially completed";
						break;

					case UploadState.InProgress:
						statusStr = "Upload running...";
						break;
				}
				return statusStr;
			}
		}
		/// <summary>
		/// Gets the count of files that currently have a pending status
		/// </summary>
		public int PendingCount
		{
			get { return this.Files.Count(f => f.State == FileStates.Pending); }
		}
		/// <summary>
		/// Gets the count of files that currently have a ready status
		/// </summary>
		public int ReadyCount
		{
			get { return this.Files.Count(f => f.State == FileStates.Ready); }
		}
		/// <summary>
		/// Gets the count of files that currently have a canceled status
		/// </summary>
		public int CanceledCount
		{
			get { return this.Files.Count(f => f.State == FileStates.Canceled); }
		}
		/// <summary>
		/// Gets the count of files that currently have a finished status
		/// </summary>
		public int FinishedCount
		{
			get { return this.Files.Count(f => f.State == FileStates.Finished); }
		}
		/// <summary>
		/// Gets the count of files that currently have errored
		/// </summary>
		public int ErroredCount
		{
			get { return this.Files.Count(f => f.State == FileStates.Error); }
		}
		/// <summary>
		/// Gets the count of files that currently have completed
		/// </summary>
		public int CompletedCount
		{
			get { return this.Files.Count(f => f.IsCompleted); }
		}
		/// <summary>
		/// Gets the Percentage of the entire upload process.
		/// </summary>
		public double Percentage
		{
			get { return _percentage; }
			internal set
			{
				_percentage = value;
				this.OnPropertyChanged("Percentage");
			}
		} double _percentage;

		/// <summary>
		/// Gets or sets the Upload Uri. Default: '/FileUpload.ashx' and must match the Handler Path in the Web.Config.
		/// </summary>
		internal Uri UploadUri
		{
			get;
			set;
		}
		/// <summary>
		/// Gets the WebConfig ApplicationSetting Key that contains the your custom Handler for the 
		/// file upload request.
		/// </summary>
		internal string HandlerKey
		{
			get;
			set;
		}
		/// <summary>
		/// Gets or sets the callback to call when the user clicks the View File Info button.
		/// </summary>
		internal Func<UploadFileViewModel, ChildWindow> ViewFileDetailCallback
		{
			get;
			set;
		}
		/// <summary>
		/// Indicates if we're debug writing.
		/// </summary>
		internal bool IsDebugWriting
		{
			get;
			set;
		}
		/// <summary>
		/// The delegate for retreiving metadata.
		/// </summary>
		internal Func<UploadFileViewModel, string> GetMetadata
		{
			get;
			set;
		}
		/// <summary>
		/// Gets the UploadDialog's Background Brush
		/// </summary>
		internal SolidColorBrush ViewBackground
		{
			get
			{
				return this.view.Background;
			}
		}

		/// <summary>
		/// Gets or sets the SelectedFile value.
		/// </summary>
		public UploadFileViewModel SelectedFile
		{
			get { return this._selectedFile; }
			set
			{
				if (this._selectedFile != value)
				{
					this._selectedFile = value;
					this.OnPropertyChanged("SelectedFile");
					if (null != this._selectedFile && !this._selectedFile.IsSelected)
						this._selectedFile.IsSelected = true;
				}
			}
		} UploadFileViewModel _selectedFile;

		#endregion

		#region Methods
		internal void Reset()
		{
			foreach (var item in this.Files)
				item.Close();
			this.Files.Clear();
			this.IsComplete = false;
			this.hasStarted = false;
			this.State = UploadState.NotStarted;
		}
		internal void BeginUpload(int maxConcurrentUploads)
		{
			this.IsComplete = false;
			this.hasStarted = false;
			this.currentCount = 0;
			this.totalBytesSent = 0;
			this.totalBytes = 0;
			this.Percentage = 0;
			this.maxConcurrentFiles = maxConcurrentUploads;
			this.State = UploadState.InProgress;

			foreach (UploadFileViewModel file in this.Files)
			{
				if (file.State != FileStates.Error)
				{
					file.SetPending(this.UploadUri);
					this.totalBytes += file.FileLength;
				}
			}

			this.hasStarted = true;
			if (!this.CheckIsComplete())
				this.ContinueUpload();
		}
		internal void CancelUpload()
		{
			if (!hasStarted)
				return;
			if (this.view.AllowNewUpload)
				this.State = UploadState.Canceled;
			else
				this.State = UploadState.CanceledDone;
			foreach (UploadFileViewModel file in this.Files)
				file.CancelUpload();
		}
		internal bool ContainsFile(string fileName)
		{
			return (0 != (from item in this.Files
						  where string.Compare(item.FileName, fileName, StringComparison.OrdinalIgnoreCase) == 0
						  select item).Count());
		}
		internal void RemoveFile(UploadFileViewModel file)
		{
			this.Files.Remove(file);
		}
		internal void Invoke(Action action)
		{
			if (this.dispatcher.CheckAccess())
				action();
			else
				this.dispatcher.BeginInvoke(action);
		}
		internal void FileProgress(long bytesSent)
		{
			Interlocked.Add(ref this.totalBytesSent, bytesSent);
			this.Percentage = (this.totalBytesSent * 100) / this.totalBytes;
		}
		internal void FileFinished()
		{
			this.OnPropertyChanged("FinishedCount");
		}
		internal void FileCompleted(string fileName, string result)
		{
			if (!hasStarted)
				return;
			Interlocked.Decrement(ref currentCount);
			this.view.OnFileUploaded(fileName, result);
			if (!this.CheckIsComplete())
				this.ContinueUpload();
		}
		internal void ShowDetail(UploadFileViewModel fileViewModel)
		{
			this.dispatcher.BeginInvoke(() =>
				{
					if (null != this.ViewFileDetailCallback)
					{
						this.fileDetail = this.ViewFileDetailCallback(fileViewModel);
						this.fileDetail.Closed += new EventHandler(fileDetail_Closed);
					}
					else
					{
						this.fileDetail = new UploadFileDetail(fileViewModel);
						this.fileDetail.Background = this.ViewBackground;
						this.fileDetail.Closed += new EventHandler(fileDetail_Closed);
						this.fileDetail.Show();
					}
				});
		}
		private void ContinueUpload()
		{
			if (!this.dispatcher.CheckAccess())
			{
				this.dispatcher.BeginInvoke(() =>
					{
						this.ContinueUpload();
					});
				return;
			}

			foreach (UploadFileViewModel file in this.Files)
			{
				if (this.State == UploadState.Canceled || this.State == UploadState.CanceledDone)
					return;
				if (file.BeginUpload())
					Interlocked.Increment(ref currentCount);
				if (currentCount >= this.maxConcurrentFiles)
					return;
			}
		}
		private bool CheckIsComplete()
		{
			if (this.CompletedCount == this.Files.Count)
			{
				if (this.State != UploadState.Canceled)
				{
					if (this.view.AllowNewUpload)
					{
						if (this.FinishedCount == this.Files.Count)
							this.State = UploadState.Completed;
						else
							this.State = UploadState.PartialComplete;
					}
					else
					{
						if (this.FinishedCount == this.Files.Count)
							this.State = UploadState.CompletedDone;
						else
							this.State = UploadState.PartialCompleteDone;
					}
				}
				this.view.ShowCompleted();
				return true;
			}
			return false;
		}
		internal void WidthChanged(double delta)
		{
			double newValue = delta + this.fileNameWidth;
			if (newValue > defaultWidth)
				this.fileNameWidth = newValue;
			else
				this.fileNameWidth = defaultWidth;

			foreach (var file in Files)
				file.UpdateFileNameWidth(this.fileNameWidth);
		}
		#endregion

		#region Event Handlers
		/// <summary>
		/// The collection changed
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void UploadFiles_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (this.view.DialogResult.HasValue)
				return;

			if (null != e.NewItems && e.NewItems.Count > 0)
			{
				foreach (UploadFileViewModel item in e.NewItems)
					item.InitializeFileNameWidth(this.fileNameWidth);
			}

			if (this.ReadyCount > 0)
			{
				if (this.view.AllowMultiselect)
					this.State = UploadState.Pending;
				else
					this.State = UploadState.PendingSingleFile;
			}
			else if (this.Files.Count == 0)
				this.State = UploadState.NotStarted;
			else if (this.ErroredCount == this.CompletedCount)
			{
				if (this.view.AllowNewUpload)
					this.State = UploadState.PartialComplete;
				else
					this.State = UploadState.PartialCompleteDone;
			}
		}
		void fileDetail_Closed(object sender, EventArgs e)
		{
			this.fileDetail.Closed -= new EventHandler(fileDetail_Closed);
			this.fileDetail = null;
			this.view.Focus();
		}
		#endregion
	}
	#endregion
}